package ws.very.util.dingtalk

import java.security.MessageDigest

import org.json4s.JValue
import org.json4s.JsonAST.JString
import ws.very.util.dingtalk.api.{CallBackServiceHelper, ChatServiceHelper, CorpUserServiceHelper, IsvServiceHelper, JsApiServiceHelper}
import ws.very.util.dingtalk.encryptor.{DingTalkEncryptException, DingTalkEncryptor}
import ws.very.util.json.JsonHelperWithDoubleMode

import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}

class DingTalkServer(env: DingTalkEnv,
                     watcher: DingTalkWatch)
  extends JsonHelperWithDoubleMode {
  val dingTalkEncryptor = new DingTalkEncryptor(env.TOKEN, env.ENCODING_AES_KEY, env.SUITE_KEY)

  private lazy val dingTalkEncryptor2 = new DingTalkEncryptor(env.TOKEN, env.ENCODING_AES_KEY, env.CREATE_SUITE_KEY)

  def listen(signature: S, timeStamp: S, nonce: S, body: JValue)(implicit executor: ExecutionContext): Future[S] = {
    val JString(encrypt) = body \ "encrypt"
    dingTalkEncryptor.getDecryptMsg(signature, timeStamp, nonce, encrypt).recoverWith {
      case DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR) =>
        dingTalkEncryptor2.getDecryptMsg(signature, timeStamp, nonce, encrypt)
    } match {
      case Success(text) =>
        val json = parseJson(text)
        val JString(eventType) = json \ "EventType"
        println("dingTalk comming.....", eventType)
        val future = eventType match {
          case "suite_ticket" =>
            val JString(suiteTicket) = json \ "SuiteTicket"
            IsvServiceHelper.getSuiteToken(env.SUITE_KEY, env.SUITE_SECRETE, suiteTicket).map { suiteToken =>
              env.changeSuite(suiteTicket, suiteToken)
              "success"
            }
          case "tmp_auth_code" =>
            val JString(authCode) = json \ "AuthCode"
            IsvServiceHelper.getPermanentCode(env.suiteToken, authCode).flatMap {
              corpAuthSuiteCode =>
                val corpId = corpAuthSuiteCode.getAuth_corp_info.getCorpid
                val permanentCode = corpAuthSuiteCode.getPermanent_code
                watcher.corpAuth(corpAuthSuiteCode)
                activateSuite(corpId).flatMap(_ =>
                  refreshJsTicketAndAccToken(corpId, permanentCode).map {
                    "success"
                  }
                )
            }
          case "change_auth" =>
            val JString(corpId) = json \ "AuthCorpId"
            watcher.appChangeStatus(corpId)
            println("change_auth success")
            Future.successful("success")
          case "check_create_suite_url" | "check_update_suite_url" =>
            val JString(random) = json \ "Random"
            Future.successful(random)

          case "suite_relieve" =>
            val JString(corpId) = json \ "AuthCorpId"
            watcher.SuiteRelease(corpId)
            Future.successful("success")

          case "check_suite_license_code" =>
            //校验序列号”事件
            val JString(licenseCode) = json \ "LicenseCode"
            watcher.licenseCode(licenseCode).map {
              "success"
            }
        }
        future.flatMap { re =>
          (if (eventType == "check_create_suite_url") {
            dingTalkEncryptor2
          } else {
            dingTalkEncryptor
          }).getEncryptedMap(re, timeStamp, nonce).map(toJson(_)) match {
            case Success(t) =>
              Future.successful(t)
            case Failure(e) => Future.failed(e)
          }
        }
      case Failure(e) =>
        println("ding talk call back fail......", e)
        Future.failed(e)
    }
  }

  def activateSuite(corpId: S)(implicit executor: ExecutionContext) = {
    IsvServiceHelper.activateSuite(env.suiteToken, env.SUITE_KEY, corpId).map {
      watcher.activateCorp(corpId)
    }
  }

  def refreshJsTicketAndAccToken(corpId: S, permanentCode: S)(implicit executor: ExecutionContext) = {
    IsvServiceHelper.getCorpToken(env.suiteToken, corpId, permanentCode).map { case (accessToken, expireTime) =>
      accessToken.foreach { accToken =>
        watcher.corpAccessToken(corpId, accToken, expireTime)
        JsApiServiceHelper.getJsapiTicket(accToken, "jsapi").foreach { case (jsTicket, et) =>
          watcher.corpJsTicket(corpId, jsTicket, et)
          println(s"refreshJsTicketAndAccToken:${corpId} ${accToken} ${jsTicket}")
        }
      }
    }
  }

  //TODO:url 还没有encode
  def jsAuth(permanentCode: S, corpId: S, appId: S, accessToken: S, jsTicket: S,
             url: S, nonceStr: S = Utils.getRandomStr(6))
            (implicit executor: ExecutionContext): Future[Map[S, S]] = {
    val timestamp = now.mill / 1000
    val signatureTry = urlSign(jsTicket, nonceStr, timestamp, url)
    IsvServiceHelper.getAuthInfo(env.suiteToken, env.SUITE_KEY, corpId, permanentCode).flatMap { corpAuthInfo =>
      val agentIdOpt = Option(corpAuthInfo.getAuth_info).flatMap(_.getAgent.find(_.getAppid.toString == appId).map(_.getAgentid))
      signatureTry -> agentIdOpt match {
        case (Success(signature), Some(agentId)) =>
          Future.successful(Map(
            "signature" -> signature,
            "nonceStr" -> nonceStr,
            "timeStamp" -> timestamp.toString,
            "agentId" -> agentId.toString
          ))
        case (Failure(e), _) => Future.failed(e)
        case (_, None) => Future.failed(new Exception("get agentId fail"))
      }
    }
  }

  def urlSign(jsTicket: S, nonceStr: S, timestamp: L, url: S) = {
    val plain = s"jsapi_ticket=$jsTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url"
    Try {
      val sha1 = MessageDigest.getInstance("SHA-1")
      sha1.reset()
      sha1.update(plain.getBytes("UTF-8"))
      Utils.bytesToHex(sha1.digest())
    }
  }

  def getAgentsInfo(corpId: S, permanentCode: S)(implicit executor: ExecutionContext) = {
    IsvServiceHelper.getAuthInfo(env.suiteToken, env.SUITE_KEY, corpId, permanentCode).flatMap { authInfo =>
      Future.sequence(authInfo.getAuth_info.getAgent.map(_.getAgentid).map(agentId =>
        IsvServiceHelper.getAgent(env.suiteToken, env.SUITE_KEY, corpId, agentId.toString)
      ).toList)
    }
  }

  def sampleCallbackEventRegister(accessToken: S, callbackTags: Seq[S], url: S)(implicit executor: ExecutionContext) =
    callbackService.registerCallBack(accessToken, callbackTags, env.TOKEN, env.ENCODING_AES_KEY, url)

  def sampleCallbackEventListen(signature: S, timeStamp: S, nonce: S, body: JValue)
                                 (func: Try[JValue] => Future[S])
                                 (implicit executor: ExecutionContext): Future[S] = {
    val JString(encrypt) = body \ "encrypt"
    func(dingTalkEncryptor.getDecryptMsg(signature, timeStamp, nonce, encrypt).map(parseJson)).flatMap { re =>
      Future.fromTry(
        dingTalkEncryptor.getEncryptedMap(re, timeStamp, nonce).map(toJson(_))
      )
    }
  }

  def isvService = IsvServiceHelper

  def callbackService = CallBackServiceHelper

  def chat(accToken: S) = new ChatServiceHelper {
    override def accessToken = accToken
  }

  def corpUserService = CorpUserServiceHelper
}
